#pragma once

#include <stdio.h>

// disk block size
#define DBSIZE 4096

// maximum disk blocks difference in one seek
#define MAX_DB_NUM 300000

// maximum filename length
#define MAX_FN_LEN 512

// replication factor
#define REP_FAC 6

#ifdef WIN32
#define _LITTLE_ENDIAN 1
#endif

#ifdef WIN32
typedef __int64 int64;
#else
typedef long long int64;
#endif          // WIN32

// Disk block pointer(index)
typedef int64 Pointer;

#ifdef WIN32
#define STRDUP(x) _strdup(x)
#else
#define STRDUP(x) strdup(x)
#endif          //WIN32

namespace PBVP {
	/// Binary I/O class to handle BIG and LITTLE endian
	class BinaryIO {
	public:
		typedef enum { READ = 0, WRITE } Type;
		typedef enum {FROM_HERE = 0, FROM_START, FROM_END} SeekMode;
		///
		BinaryIO(const char* fname, BinaryIO::Type mode = BinaryIO::READ, int buf_size = NDB);
		///
		BinaryIO(FILE* fp, BinaryIO::Type mode = BinaryIO::READ, int buf_size = NDB);
		///
		virtual ~BinaryIO();

		///
		Type mode() const { return m_mode;}

		/**@name Get Functions 
		* overloaded get functions for various data type
		*/
		//@{
		///
		int get(char* data, int size);
		///
		int get(unsigned char* data, int size);
		///
		int get(short* data, int size);
		///
		int get(unsigned short* data, int size);
		///
		int get(int* data, int size);
		/// 
		int get(unsigned int* data, int size) {
			return get((int*)data, size);
		}
		///
		int get(long* data, int size);
		///
		int get(int64* data, int size);
		///
		int get(float* data, int size);
		///
		int get(double* data, int size);
		//@}

		/**@name Put Functions 
		* overloaded put functions for various data type
		*/
		//@{
		///
		void put(const char* data, int size);
		///
		void put(const unsigned char* data, int size);
		///
		void put(const short* data, int size);
		///
		void put(const unsigned short* data, int size);
		///
		void put(const int* data, int size);
		/// 
		void put(const unsigned int* data, int size) {
			put((int *)data, size);
		}
		///
		void put(const long* data, int size);
		///
		void put(const int64* data, int size);
		///
		void put(const float* data, int size);
		///
		void put(const double* data, int size);
		//@}

		/// open the disk IO for read or write
		bool open();

		/// open another file
		bool reopen(const char *fname);

		/**	reopen the current file
		* 	write files will be opened in append mode
		*/ 
		bool reopen();

		/// close the disk IO
		bool close(bool fill = true);

		/** In the case of write mode : write out everything in buffer 
		and clear the buffer. If fill = true, file will be appended until next
		disk block boundary.
		In the case of read mode : move pointer to the beginnign of next disk block
		This is useful when reader want to skip stuff in current disk block.
		*/
		void flush(bool fill = true);

		/// inherited seek function
		bool seek(long offset, BinaryIO::SeekMode mode);

		/// change file access type
		bool setMode(BinaryIO::Type type);

		/// end of file has been reached
		bool eof() {return (m_eof && m_bufptr == m_bufsize);}

	private:
		/**@name Primitive I/O operations
		* raw read and write functions 
		*/
		//@{
		/** get #n# bytes from buffer, go to disk if necessary
		@param size unit record size
		@param n	number of records
		@return number of records actually read
		*/
		int getraw(void *data, int size, int n);
		/// put #n# bytes to buffer, flust if necessary
		void putraw(const void *data, int size);
		//@}

		void init();			// initialize the buffer

		const static int NDB;
		char* m_fname;
		FILE* m_fp;
		//char m_buffer[DBSIZE * NDB];
		int m_ndb;
		char* m_buffer;
		int  m_total;			// the whole size of the total buffer = NDB*DBSIZE
		int  m_bufsize;         // current size of buffer
		int  m_bufptr;          // current position of the buffer
		bool m_eof;

		Type m_mode;

	};
}